module wad;

debug import tame.io.stdio, tame.logger;
import core.sys.windows.windows,
tame.fixed.set,
tame.unsafe.string,
wad.browserfinder,
wad.process;

/// A fixed-size set of window handles.
alias WSet = Set!(HWND, 256);

struct WebAppDriver {
	Pid pid;
	private {
		WSet hwnds;
		const(char)[] exePath;
	}

	/++
	Open a web app in a browser window.
	Params:
		url = The URL of the web app.
		dataDir = The user data directory.
	Throws:
		ProcessException
	+/
	this(in char[] url, in char[] dataDir = null) {
		StrBuilder s;
		findBrowser(s);
		exePath = s.str.dup;
		getChromeWindows(hwnds);
		s ~= " --allow-file-access-from-files";
		s ~= " --app=";
		s ~= url;
		if (dataDir.length) {
			s ~= " --user-data-dir=";
			s ~= dataDir;
		}
		debug writeln("Starting ", s.str);
		pid = startProcess(exePath, s.str);
	}

	@disable this(this);

	/++
	Get the window handle of the browser window.
	Params:
		title = The title of the browser window,
				or null to get the first window.
	Returns:
		The window handle, or null if not found.
	+/
	HWND getWindow(in char[] title = null) nothrow
	in (exePath.length) {
		StrBuilder s;
		s ~= title;
		return getChromeWindows(hwnds, title.length ? s.strz.ptr : null, exePath);
	}
}

version (testWAD) unittest {
	auto app = WebAppDriver("https://bing.com");
	Sleep(2500);
	HWND hwnd = app.getWindow();
	writeln(hwnd);
}

HWND getChromeWindows(ref WSet hwnds, LPCSTR title = null, in char[] file = null) nothrow @trusted {
	import core.stdc.string,
	tame.io.path,
	tame.string : fromStringz;

	// import core.sys.windows.psapi : GetModuleFileNameExA;

	HWND hwnd = void;
	do {
		hwnd = FindWindowExA(null, hwnd, "Chrome_WidgetWin_1", title);
		enum style = WS_VISIBLE | WS_OVERLAPPEDWINDOW;
		if ((GetWindowLong(hwnd, GWL_STYLE) & style) == style &&
			!GetWindow(hwnd, GW_OWNER) &&
			!hwnds.has(hwnd)) {
			if (file.length) {
				DWORD pid = void;
				const r = GetWindowThreadProcessId(hwnd, &pid);
				assert(r);
				auto handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
				char[MAX_PATH] buf = void;
				GetModuleFileNameExA(handle, null, buf.ptr, buf.length);
				const path = fromStringz(buf.ptr);
				const dirLen = path.length - baseName(path).length;
				if (strncmp(path.ptr, file.ptr, dirLen) == 0) {
					CloseHandle(handle);
					break;
				}
			}
			hwnds.add(hwnd);
		}
	}
	while (hwnd);
	return hwnd;
}

private extern (Windows) DWORD GetModuleFileNameExA(HANDLE, HMODULE, LPSTR, DWORD) nothrow @nogc;

/// A file iterator for the file dialog.
struct FileIterator {
	import core.exception,
	core.stdc.stdlib,
	core.stdc.wchar_,
	tame.conv : text;

	this(wchar* s) {
		buf = s;
		if (s) {
			len = wcslen(s);
			// Find the first null character
			// to get the file name.
			// The file name is separated by a null character.
			if (*(p = s + len + 1)) {
				const d = text(s[0 .. len], '\\');
				dir = (cast(char*)malloc(d.length))[0 .. d.length];
				if (!dir)
					onOutOfMemoryError();
				dir[0 .. d.length] = d;
			} else
				p = s;
		}
	}

	@disable this(this);

	~this() {
		free(buf);
		free(dir.ptr);
	}

	bool empty() const => !p || !*p;

	@property string front() {
		len = wcslen(p);
		return text(dir, p[0 .. len]);
	}

	void popFront() {
		p += len + 1;
	}

private:
	wchar* buf, p;
	size_t len;
	char[] dir;
}

auto browseFiles(bool multi = false, HWND hwnd = null) {
	import core.stdc.stdlib;

	OPENFILENAME ofn;
	const maxlen = cast(uint)MAX_PATH * (multi ? 100 : 1);
	auto p = cast(wchar*)calloc(maxlen, wchar.sizeof);
	//char[MAX_PATH] szFileTitle = void;
	ofn.hwndOwner = hwnd;
	ofn.lpstrFilter = "All Files\0*.*\0";
	//ofn.lpstrCustomFilter = null;
	//ofn.nMaxCustFilter = 0;
	//ofn.nFilterIndex = 0;
	ofn.lpstrFile = p;
	ofn.nMaxFile = maxlen;
	//ofn.lpstrFileTitle = szFileTitle.ptr;
	//ofn.nMaxFileTitle = szFileTitle.length;
	//ofn.lpstrInitialDir = null;
	//ofn.lpstrTitle = null;
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER;
	if (multi)
		ofn.Flags |= OFN_ALLOWMULTISELECT;
	if (!GetOpenFileName(&ofn)) {
		free(p);
		p = null;
		const err = CommDlgExtendedError();
		debug error(err);
		if (err == FNERR_BUFFERTOOSMALL)
			throw new Exception("Too many files selected");
	}
	return FileIterator(p);
}
